<?php

// +----------------------------------------------------------------------
// | 悟空信息技术有限公司
// +----------------------------------------------------------------------
// | Copyright (c)2016 http://www.wkidt.com, All rights reserved.
// +----------------------------------------------------------------------
// | Author: wkidt team LSQ <admin@wkidt.com> 2017/6/3 10:39
// +----------------------------------------------------------------------
// | Readme: 资源逻辑类
// +----------------------------------------------------------------------

namespace app\system\logic;

use app\system\exception\AccessDeniedException;
use app\system\exception\AuthInvalidException;
use app\system\interfaces\AuthServiceProvider;
use think\Cache;
use think\Config;
use app\system\model\Resource as ResourceModel;
use think\Db;
use think\Lang;
use think\Route;
use Wkidt\think5\App;
use Wkidt\think5\exception\Exception;

class Resource
{
    /**
     * 实例
     *
     * @var null
     */
    protected static $instance = null;

    /**
     * 注册的文件
     *
     * @var array
     */
    protected static $files = [];

    /**
     *
     *
     * @var AuthServiceProvider
     */
    protected $authServiceProvider = [];

    /**
     * 获取一个实例
     * @return static
     */
    public static function instance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    /**
     * 禁止new
     *
     * Dictionary constructor.
     */
    private function __construct()
    {

    }

    /**
     * 添加授权器
     *
     * @param AuthServiceProvider $authServiceProvider
     */
    public function addAuthServiceProvider(AuthServiceProvider $authServiceProvider)
    {
        $this->authServiceProvider[] = $authServiceProvider;
    }

    /**
     * 加载授权器
     *
     * @return AuthServiceProvider
     */
    public function loadAuthServiceProvider()
    {
        if (false == $this->authServiceProvider) {
            Event::trigger('load_auth_service_provider', $this);
        }
        return $this->authServiceProvider;
    }

    /**
     * 验证访问权限
     *
     * @param $code
     * @return bool
     * @throws \Exception
     */
    public function accessControl($code)
    {
        $hasAuth = false;
        foreach ($this->loadAuthServiceProvider() as $authServiceProvider) {
            /**
             *
             * @var $authServiceProvider AuthServiceProvider
             */
            if ($authServiceProvider instanceof AuthServiceProvider) {
                if ($authServiceProvider->check($code)) {
                    return true;
                } elseif ($authServiceProvider->hasAuth()) {
                    $hasAuth = true;
                }
            }
        }

        if ($hasAuth) {
            throw new AccessDeniedException();
        } else {
            throw new AuthInvalidException();
        }
    }

    /**
     * 将资源导入到路由
     *
     * @throws Exception
     */
    public function importToRoute()
    {
        $routes = Cache::get(Config::get('app_id') . '_routes');
        if (false == $routes) {
            $data = ResourceModel::all(['app' => Config::get('app_id')]);
            foreach ($data as $val) {
                try {
                    $rules = json_decode($val['rule'], true);
                    if (!empty($rules) && is_array($rules)) {
                        foreach ($rules as $key => &$rule) {
                            // 封装权限码到路由参数
                            if (is_string($rule)) {
                                $rule = [$rule, ['code' => $val['code']]];
                            } else {
                                $i = is_numeric($key) ? '2' : '1';
                                if (isset($rule[$i])) {
                                    $rule[$i]['code'] = $val['code'];
                                } else {
                                    $rule[$i] = ['code' => $val['code']];
                                }
                            }

                            // 判断请求类型 并重新封装数组
                            $type = (isset($i) && isset($rule[$i]['method'])) ? $rule[$i]['method'] : '*';
                            !is_numeric($key) && array_unshift($rule, $key);
                            Route::rule([$rule], '', $type);
                        }
                    }
                } catch (\Exception $e) {
                    throw App::$debug ? new Exception(Lang::get('resource configuration error') . ":{$val['group']}->{$val['name']}->{$val['operation']}") : $e;
                }
            }
            Cache::tag(Config::get('app_id') . '_resource')
                ->set(Config::get('app_id') . '_routes', Route::rules(true));
        } else {
            Route::rules($routes);
        }
    }

    /**
     * 解析所有资源：包括所有模块的资源 注册的文件资源 配置的文件资源
     *
     */
    public function updateAll()
    {
        Db::startTrans();
        if (false !== ResourceModel::destroy(['app' => Config::get('app_id')])) {
            try {
                $files = $this->getAllResourceFiles();
                foreach ($files as $file) {
                    if (false === $this->update($file)) {
                        Db::rollback();
                        return false;
                    }
                }
            } catch (\Exception $e) {
                Db::rollback();
                throw $e;
            }
        }

        try {
            $res = file_put_contents(RUNTIME_PATH . 'resource.lock', $this->getResourceInfoKey());
            if (false === $res) {
                Db::rollback();
                return false;
            } else {
                Db::commit();

                // 清除资源缓存
                Cache::clear(Config::get('app_id') . '_resource');
                return true;
            }
        } catch (\Exception $e) {
            if (App::$debug) {
                throw $e;
            } else {
                return false;
            }
        }
    }

    /**
     * 检测是否有资源更新
     *
     * @return bool
     */
    public function isUpdate()
    {
        $lockFile = RUNTIME_PATH . 'resource.lock';
        if (file_exists($lockFile)) {
            if (\think\App::$debug) {
                $oldResourceKey = file_get_contents($lockFile);
                if ($oldResourceKey != $this->getResourceInfoKey()) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

    /**
     * 获取所有资源配置文件
     *
     * @return array
     */
    public function getAllResourceFiles()
    {
        return array_merge(self::$files, (array)Config::get('resource_file'), $this->getModuleResourceFiles());
    }

    /**
     * 获取资源信息的md5值
     *
     * @return string
     */
    protected function getResourceInfoKey()
    {
        $files = $this->getAllResourceFiles();
        $fileInfo = '';
        foreach ($files as $file) {
            $fileInfo .= $file . filemtime($file);
        }
        return md5($fileInfo);
    }

    /**
     * 更新单个文件
     *
     * @param $filename
     * @return bool
     * @throws Exception
     */
    protected function update($filename)
    {
        static $codes = [];
        if (!file_exists($filename)) {
            return true;
        }

        $data = include_once $filename;
        if (true === $data && false == $data) {
            return true;
        } else {
            $resourceData = [];
            foreach ($data as $groupName => $resources) {
                foreach ($resources as $resourceName => $operations) {
                    foreach ($operations as $operationName => $rules) {
                        // 提取出权限码
                        if (false === strpos($operationName, '@')) {
                            $code = '';
                        } else {
                            list($operationName, $code) = explode('@', $operationName, 2);
                        }
                        if ('' == $code) {
                            foreach ($rules as $key => $rule) {
                                if (is_string($rule)) {
                                    $code = $rule;
                                } elseif (is_numeric($key) && is_array($rule)) {
                                    $code = $rule[1];
                                } else {
                                    $code = $rule[0];
                                }
                                break;
                            }
                        }

                        // 权限码是否重复
                        if (in_array($code, $codes)) {
                            throw new Exception('权限码必须是唯一的：' . $code, 'code_not_unique');
                        } else {
                            $codes[] = $code;
                        }

                        // 准备数据
                        $resourceData[] = [
                            'app' => Config::get('app_id'),
                            'group' => $groupName,
                            'name' => $resourceName,
                            'operation' => $operationName,
                            'code' => $code,
                            'rule' => json_encode($rules),
                        ];
                    }
                }
            }
            if (false === ResourceModel::instance()->saveAll($resourceData)) {
                return false;
            } else {
                return true;
            }
        }
    }

    /**
     * 注册资源配置文件
     *
     * @param $filename
     * @return bool
     */
    public static function file($filename)
    {
        if (file_exists($filename)) {
            self::$files[] = $filename;
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取所有模块资源文件
     *
     * @return array
     */
    protected function getModuleResourceFiles()
    {
        static $files = [];
        if (false == $files) {
            $dirs = scandir(APP_PATH);
            foreach ($dirs as $dir) {
                $filename = APP_PATH . $dir . DS . 'resource.php';
                if (is_dir(APP_PATH . $dir) && file_exists($filename)) {
                    $files[] = $filename;
                }
            }
        }
        return $files;
    }
}
